/********************************************************************************
 * Copyright (c) 2011-2017 Red Hat Inc. and/or its affiliates and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 1.0 which is available at
 * http://www.eclipse.org/legal/epl-v10.html.
 *
 * SPDX-License-Identifier: EPL-1.0
 ********************************************************************************/
package org.eclipse.ceylon.ide.eclipse.code.navigator;

import static org.eclipse.ceylon.ide.eclipse.java2ceylon.Java2CeylonProxies.modelJ2C;
import static org.eclipse.ceylon.ide.eclipse.util.CeylonHelper.toJavaStringList;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.actions.ActionMessages;
import org.eclipse.jdt.internal.ui.actions.JarImportWizardAction;
import org.eclipse.jdt.internal.ui.packageview.ClassPathContainer;
import org.eclipse.jdt.internal.ui.packageview.PackageFragmentRootContainer;
import org.eclipse.jdt.internal.ui.preferences.BuildPathsPropertyPage;
import org.eclipse.jdt.internal.ui.wizards.NewWizardMessages;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.CPListElement;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage.AddArchiveToBuildpathAction;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage.AddFolderToBuildpathAction;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage.AddLibraryToBuildpathAction;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage.AddSelectedLibraryToBuildpathAction;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage.ConfigureBuildPathAction;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage.CreateLinkedSourceFolderAction;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage.CreateSourceFolderAction;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage.EditFilterAction;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage.EditOutputFolderAction;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage.ExcludeFromBuildpathAction;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage.IncludeToBuildpathAction;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage.RemoveFromBuildpathAction;
import org.eclipse.jdt.ui.IContextMenuConstants;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchSite;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionGroup;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.part.Page;
import org.eclipse.ui.texteditor.IUpdate;

import org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonBuildPathsBlock;
import org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonBuildPathsPropertiesPage;
import org.eclipse.ceylon.ide.eclipse.core.external.ExternalSourceArchiveManager;
import org.eclipse.ceylon.ide.common.model.BaseCeylonProject;
import org.eclipse.ceylon.ide.common.model.CeylonProjectConfig;

/**
 * Copied from <code>{@link org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage.GenerateBuildPathActionGroup}</code>
 * to override some of the actions.
 */
public class GenerateBuildPathActionGroup extends ActionGroup {
    /**
     * Pop-up menu: id of the source sub menu (value <code>org.eclipse.jdt.ui.buildpath.menu</code>).
     *
     * @since 3.1
     */
    public static final String MENU_ID= "org.eclipse.jdt.ui.buildpath.menu"; //$NON-NLS-1$

    /**
     * Pop-up menu: id of the build path (add /remove) group of the build path sub menu (value
     * <code>buildpathGroup</code>).
     *
     * @since 3.1
     */
    public static final String GROUP_BUILDPATH= "buildpathGroup";  //$NON-NLS-1$

    /**
     * Pop-up menu: id of the filter (include / exclude) group of the build path sub menu (value
     * <code>filterGroup</code>).
     *
     * @since 3.1
     */
    public static final String GROUP_FILTER= "filterGroup";  //$NON-NLS-1$

    /**
     * Pop-up menu: id of the customize (filters / output folder) group of the build path sub menu (value
     * <code>customizeGroup</code>).
     *
     * @since 3.1
     */
    public static final String GROUP_CUSTOMIZE= "customizeGroup";  //$NON-NLS-1$

    private static class NoActionAvailable extends Action {
        public NoActionAvailable() {
            setEnabled(false);
            setText(NewWizardMessages.GenerateBuildPathActionGroup_no_action_available);
        }
    }
    private Action fNoActionAvailable= new NoActionAvailable();

    private class UpdateJarFileAction extends JarImportWizardAction implements IUpdate {

        public UpdateJarFileAction() {
            setText(ActionMessages.GenerateBuildPathActionGroup_update_jar_text);
            setDescription(ActionMessages.GenerateBuildPathActionGroup_update_jar_description);
            setToolTipText(ActionMessages.GenerateBuildPathActionGroup_update_jar_tooltip);
            setImageDescriptor(JavaPluginImages.DESC_OBJS_JAR);
            PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IJavaHelpContextIds.JARIMPORT_WIZARD_PAGE);
        }

        /**
         * {@inheritDoc}
         */
        public void update() {
            final IWorkbenchPart part= fSite.getPage().getActivePart();
            if (part != null)
                setActivePart(this, part);
            selectionChanged(this, fSelectionProvider.getSelection());
        }
    }

    private final IWorkbenchSite fSite;
    private final ISelectionProvider fSelectionProvider;
    private final List<Action> fActions;

    private String fGroupName= IContextMenuConstants.GROUP_REORGANIZE;


    /**
     * Creates a new <code>GenerateActionGroup</code>. The group
     * requires that the selection provided by the page's selection provider
     * is of type <code>org.eclipse.jface.viewers.IStructuredSelection</code>.
     *
     * @param page the page that owns this action group
     */
    public GenerateBuildPathActionGroup(Page page) {
        this(page.getSite(), page.getSite().getSelectionProvider());
    }

    /**
     * Creates a new <code>GenerateActionGroup</code>. The group
     * requires that the selection provided by the part's selection provider
     * is of type <code>org.eclipse.jface.viewers.IStructuredSelection</code>.
     *
     * @param part the view part that owns this action group
     */
    public GenerateBuildPathActionGroup(IViewPart part) {
        this(part.getSite(), part.getSite().getSelectionProvider());
    }
    /**
     * Creates a new <code>GenerateActionGroup</code>. The group requires
     * that the selection provided by the given selection provider is of type
     * {@link IStructuredSelection}.
     *
     * @param site the site that will own the action group.
     * @param selectionProvider the selection provider used instead of the
     *  page selection provider.
     *
     * @since 3.4
     */
    public GenerateBuildPathActionGroup(IWorkbenchSite site, ISelectionProvider selectionProvider) {
        fSite= site;
        fSelectionProvider= selectionProvider;
        fActions= new ArrayList<Action>();

        final CreateLinkedSourceFolderAction addLinkedSourceFolderAction= new CreateLinkedSourceFolderAction(site) {
            private void doBuildpathAction() {
                super.run();
            }
            @Override
            public void run() {
                alsoManageCeylonConfigFile(new Runnable() {
                    @Override
                    public void run() {
                        doBuildpathAction();
                    }
                }, getShell(), getSelectedElements());
            }
        };
        fActions.add(addLinkedSourceFolderAction);

        final CreateSourceFolderAction addSourceFolderAction= new CreateSourceFolderAction(site) {
            private void doBuildpathAction() {
                super.run();
            }
            @Override
            public void run() {
                alsoManageCeylonConfigFile(new Runnable() {
                    @Override
                    public void run() {
                        doBuildpathAction();
                    }
                }, getShell(), getSelectedElements());
            }
        };
        fActions.add(addSourceFolderAction);

        final AddFolderToBuildpathAction addFolder= new AddFolderToBuildpathAction(site) {
            private void doBuildpathAction() {
                super.run();
            }
            @Override
            public void run() {
                alsoManageCeylonConfigFile(new Runnable() {
                    @Override
                    public void run() {
                        doBuildpathAction();
                    }
                }, getShell(), getSelectedElements());
            }
        };
        fActions.add(addFolder);

        final AddSelectedLibraryToBuildpathAction addSelectedLibrary= new AddSelectedLibraryToBuildpathAction(site);
        fActions.add(addSelectedLibrary);

        final RemoveFromBuildpathAction remove = new RemoveFromBuildpathAction(site) {
            private void doBuildpathAction() {
                super.run();
            }
            @Override
            public void run() {
                alsoManageCeylonConfigFile(new Runnable() {
                    @Override
                    public void run() {
                        doBuildpathAction();
                    }
                }, getShell(), getSelectedElements());
            }
        };
        fActions.add(remove);

        final AddArchiveToBuildpathAction addArchive= new AddArchiveToBuildpathAction(site);
        fActions.add(addArchive);

        final AddLibraryToBuildpathAction addLibrary= new AddLibraryToBuildpathAction(site);
        fActions.add(addLibrary);

        final UpdateJarFileAction updateAction= new UpdateJarFileAction();
        fActions.add(updateAction);

        final ExcludeFromBuildpathAction exclude= new ExcludeFromBuildpathAction(site);
        fActions.add(exclude);

        final IncludeToBuildpathAction include= new IncludeToBuildpathAction(site);
        fActions.add(include);

        final EditFilterAction editFilterAction= new EditFilterAction(site);
        fActions.add(editFilterAction);

        final EditOutputFolderAction editOutput= new EditOutputFolderAction(site);
        fActions.add(editOutput);

        final ConfigureBuildPathAction configure= new ConfigureBuildPathAction(site) {
            @Override
            public void run() {
                IProject project = null;
                Object firstElement = getSelectedElements().get(0);
                HashMap<Object, IClasspathEntry> data= new HashMap<Object, IClasspathEntry>();

                if (firstElement instanceof IJavaElement) {
                    IJavaElement element= (IJavaElement) firstElement;
                    IPackageFragmentRoot root= (IPackageFragmentRoot) element.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
                    if (root != null) {
                        try {
                            data.put(BuildPathsPropertyPage.DATA_REVEAL_ENTRY, root.getRawClasspathEntry());
                        } catch (JavaModelException e) {
                            // ignore
                        }
                    }
                    project = element.getJavaProject().getProject();
                }
                else if (firstElement instanceof PackageFragmentRootContainer) {
                    PackageFragmentRootContainer container= (PackageFragmentRootContainer) firstElement;
                    project= container.getJavaProject().getProject();
                    IClasspathEntry entry = container instanceof ClassPathContainer ? ((ClassPathContainer) container).getClasspathEntry() : JavaCore.newLibraryEntry(new Path("/x/y"), null, null); //$NON-NLS-1$
                    data.put(BuildPathsPropertyPage.DATA_REVEAL_ENTRY, entry);
                }
                else {
                    project= ((IResource) ((IAdaptable) firstElement).getAdapter(IResource.class)).getProject();
                }
                PreferencesUtil.createPropertyDialogOn(getShell(), project, CeylonBuildPathsPropertiesPage.ID, null, data).open();
            }
        };
        fActions.add(configure);

        for (Iterator<Action> iter= fActions.iterator(); iter.hasNext();) {
            Action action= iter.next();
            if (action instanceof ISelectionChangedListener) {
                ISelectionChangedListener listener = (ISelectionChangedListener) action;
                selectionProvider.addSelectionChangedListener(listener);
                listener.selectionChanged(new SelectionChangedEvent(selectionProvider, selectionProvider.getSelection()));
            }
        }

    }

    public static void alsoManageCeylonConfigFile(Runnable runnable, Shell shell, List<?> selectedElements) {
        // First check the the config is synchronized to authorize the action
        IProject project = null;
        IJavaProject javaProject = null;
        Object object= selectedElements.get(0);
        if (object instanceof IJavaElement) {
            IJavaElement javaElement = (IJavaElement) object;
            javaProject = javaElement.getJavaProject();
        } else if (object instanceof IResource) {
            IResource resource = (IResource) object;
            if (!ExternalSourceArchiveManager.isInSourceArchive(resource)) {
                javaProject= JavaCore.create(resource.getProject());
            }
        }
        
        if (javaProject != null) {
            project = javaProject.getProject();
        }

        if (project == null) {
            return;
        }
        
        BaseCeylonProject ceylonProject = 
                modelJ2C()
                    .ceylonModel()
                    .getProject(project);
        
        if (ceylonProject != null
                && !ceylonProject.getSynchronizedWithConfiguration()) {
            MessageDialog.openError(shell, 
                    NewWizardMessages.NewSourceContainerWorkbookPage_ToolBar_RemoveFromCP_tooltip, 
                    "The Ceylon configuration file (.ceylon/config) is not synchronized with the current build path settings.\n" +
                    "You should fix this before adding changing the Ceylon build path (a QuickFix is available).");
            return;
        }
        
        // Then run the modification action
        runnable.run();
        
        if (ceylonProject != null) {
            CeylonProjectConfig config = ceylonProject.getConfiguration();
    
            // And finally update the Ceylon config file
            try {
                CPListElement[] classPathEntries = 
                        CPListElement.createFromExisting(javaProject);
                List<CPListElement> resourcePathEntries = new ArrayList<>();
                CeylonBuildPathsBlock.resourcePathsFromStrings(javaProject, 
                        toJavaStringList(config.getProjectResourceDirectories()), 
                        new HashSet<String>(), resourcePathEntries);
                CeylonBuildPathsBlock.flush(Arrays.asList(classPathEntries), 
                        resourcePathEntries, javaProject.readOutputLocation(), 
                        javaProject, null, null);
            }
            catch (OperationCanceledException|CoreException e) {}
        }
    }
    
    /* (non-Javadoc)
     * Function declared in ActionGroup
     */
    @Override
    public void fillActionBars(IActionBars actionBar) {
        super.fillActionBars(actionBar);
        setGlobalActionHandlers(actionBar);
    }

    /* (non-Javadoc)
     * Function declared in ActionGroup
     */
    @Override
    public void fillContextMenu(IMenuManager menu) {
        super.fillContextMenu(menu);
        if (!canOperateOnSelection())
            return;
        String menuText= ActionMessages.BuildPath_label;
        IMenuManager subMenu= new MenuManager(menuText, MENU_ID);
        subMenu.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                fillViewSubMenu(manager);
            }
        });
        subMenu.setRemoveAllWhenShown(true);
        subMenu.add(new ConfigureBuildPathAction(fSite));
        menu.appendToGroup(fGroupName, subMenu);
    }

    private void fillViewSubMenu(IMenuManager source) {
        int added= 0;
        int i=0;
        for (Iterator<Action> iter= fActions.iterator(); iter.hasNext();) {
            Action action= iter.next();
            if (action instanceof IUpdate)
                ((IUpdate) action).update();

            if (i == 2)
                source.add(new Separator(GROUP_BUILDPATH));
            else if (i == 8)
                source.add(new Separator(GROUP_FILTER));
            else if (i == 10)
                source.add(new Separator(GROUP_CUSTOMIZE));
            added+= addAction(source, action);
            i++;
        }

        if (added == 0) {
            source.add(fNoActionAvailable);
        }
    }

    /**
     * @param actionBar the action bars to set the handler for
     */
    private void setGlobalActionHandlers(IActionBars actionBar) {
        // TODO implement
    }

    private int addAction(IMenuManager menu, IAction action) {
        if (action != null && action.isEnabled()) {
            menu.add(action);
            return 1;
        }
        return 0;
    }

    private boolean canOperateOnSelection() {
        ISelection sel= fSelectionProvider.getSelection();
        if (!(sel instanceof IStructuredSelection))
            return false;
        IStructuredSelection selection= (IStructuredSelection)sel;
        if (selection.isEmpty())
            return false;
        for (Iterator<?> iter= selection.iterator(); iter.hasNext();) {
            Object element= iter.next();
            if (element instanceof IWorkingSet)
                return false;
        }
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void dispose() {
        if (fActions != null) {
            for (Iterator<Action> iter= fActions.iterator(); iter.hasNext();) {
                Action action= iter.next();
                if (action instanceof ISelectionChangedListener)
                    fSelectionProvider.removeSelectionChangedListener((ISelectionChangedListener) action);
            }
            fActions.clear();
        }
        super.dispose();
    }
}
